package com.imooc.SmartChargeStation.demo.netty;

import com.imooc.SmartChargeStation.demo.netty.handlers.ImoocClientHandler;
import com.imooc.SmartChargeStation.demo.netty.handlers.ImoocClientPkgHandler;
import com.imooc.SmartChargeStation.demo.netty.handlers.ImoocClientProtobufHandler;
import com.imooc.SmartChargeStation.demo.netty.handlers.ImoocServerPkgHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * author: zpy
 * description: Netty 客户端固定模板
 * date: 2025/8/20
 */

@Component
/*不同点1：客户端在服务端启动后再启动*/
@Order(2)
@Slf4j
public class NettyClient implements CommandLineRunner {
/*
    怎么理解NettyClient(客户端)和NettyServer(服务端)的区别？
    从代码的不同处来理解。
*/

/*不同点2：客户端使用单一线程组处理连接和io*/
    private NioEventLoopGroup eventLoop;

    private Channel channel;

/*不同点3：客户端依赖服务端地址和端口*/
    @Value("${Netty.server.port}")
    private int port;

    @Value("${Netty.server.host}")
    private String host;

    /**
     * description: Netty启动
     */
    public void start() {
        eventLoop = new NioEventLoopGroup();
/*不同点4：客户端使用Bootstrap启动器*/
        Bootstrap bootstra = new Bootstrap();
        bootstra
                .group(eventLoop)
/*不同点5：使用NioSocketChannel客户端TCP通道*/
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<>() {
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                        //方案1：分隔符方式
                        //定义分隔符，在pipeline上写下面的代码
                        //ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                        pipeline
/*不同点6：客户端发送Protobuf消息*/
                                //方案1：分隔符方式
                                // 分隔符帧解码器，最大长度1024
                                //.addLast(new DelimiterBasedFrameDecoder(1024, delimiter))
                                // 字符串解码器，将ByteBuf转为String
                                //.addLast(new StringDecoder())

                                //方案2：固定长度方式
                                // 固定长度帧解码器，长度11
                                //.addLast(new FixedLengthFrameDecoder(8))

                                //方案3：Protobuf序列
                                //在发送 Protobuf 消息时，自动在消息字节流前添加一个 “长度前缀”。（编码器）
                                //.addLast(new ProtobufVarint32LengthFieldPrepender())
                                //添加Protobuf编码器
                                //.addLast(new ProtobufEncoder())
                                //添加Protobuf消息实例发送器
                                //.addLast(new ImoocClientProtobufHandler())

                                //添加字符串编码器
                                //.addLast(new StringEncoder())
                                //粘包半包问题场景复现客户端处理器
                                //.addLast(new ImoocClientPkgHandler());


                                //自定义处理器
                                .addLast(new ImoocClientHandler())
/*
//粘包半包问题场景复现代码：
                                //添加字符串编码器
                                .addLast(new StringEncoder())
                                //粘包半包问题场景复现客户端处理器
                                .addLast(new ImoocClientPkgHandler());
 */


//测试protocolbuf编解码器客户端代码：
                                //添加Protobuf编码器
                                //.addLast(new ProtobufEncoder())
                                //添加Protobuf消息实例发送器
                                //.addLast(new ImoocClientProtobufHandler())
                        ;
                    }
                });
        doConnect(bootstra);
    }

    public void doConnect(Bootstrap bootstra) {
        log.info(">>>>>Netty 客户端启动中...");
        ChannelFuture future = null;
        try {
/*不同点7：客户端主动连接服务端*/
            future = bootstra.connect(host, port).sync();
            log.info(">>>>>Netty客户端开始监听: {}:{}<<<<<", host, port);
            future.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if (channelFuture.isSuccess()) {
                        log.info(">>>>>Netty 客户端连接成功");
                    } else {
                        log.info(">>>>>Netty 客户端和服务端断开了连接, 重连中...");
/*不同点8：客户端连接失败后定时重连*/
                        final EventLoop loop = channelFuture.channel().eventLoop();
                        loop.schedule(new Runnable() {
                            @Override
                            public void run() {
                                doConnect(bootstra);
                            }
                        }, 10, TimeUnit.SECONDS);
                    }
                }
            });
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            log.info(">>>>>Netty客户端关闭....");
            destory();
        }
    }

    /**
     * description: Netty 关闭
     */
    @PreDestroy
    public void destory() {
        try {
            if (eventLoop != null)
                eventLoop.shutdownGracefully().sync();
            if (channel != null)
                channel.closeFuture().syncUninterruptibly();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * description: Netty Client以另一个线程启动
     */
    @Async
    @Override
    public void run(String... args) throws Exception {
        start();
    }
}
